package deepr.db;

import deepr.src.DeeprMapper;
import org.apache.ibatis.builder.StaticSqlSource;
import org.apache.ibatis.exceptions.TooManyResultsException;
import org.apache.ibatis.mapping.*;
import org.apache.ibatis.scripting.LanguageDriver;
import org.apache.ibatis.session.Configuration;
import org.apache.ibatis.session.SqlSession;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.context.annotation.Bean;
import org.springframework.stereotype.Component;
import org.springframework.stereotype.Repository;

import java.util.ArrayList;
import java.util.List;
import java.util.Map;

@Repository
public class SqlMapper implements PgMapper {

  private final MSUtils msUtils;
  private final SqlSession sqlSession;

  @Autowired
  public SqlMapper(SqlSession sqlSession) {
    this.sqlSession = sqlSession;
    this.msUtils = new MSUtils(this.sqlSession.getConfiguration());
  }

  public List<Map<String, Object>> queryList(String sql, Map<String, Object> filter) {
    return selectList(sql, filter);
  }

  public Map<String, Object> queryInfo(String sql, Map<String, Object> filter) {
    return selectOne(sql, filter);
  }

  /**
   * 获取List中最多只有一个的数据
   *
   * @param list List结果
   * @param <T>  泛型类型
   * @return
   */
  private <T> T getOne(List<T> list) {
    if (list.size() == 1) {
      return list.get(0);
    } else if (list.size() > 1) {
      throw new TooManyResultsException("Expected one result (or null) to be returned by selectOne(), but found: " + list.size());
    } else {
      return null;
    }
  }

  /**
   * 查询返回一个结果，多个结果时抛出异常
   *
   * @param sql 执行的sql
   * @return
   */
  public Map<String, Object> selectOne(String sql) {
    List<Map<String, Object>> list = selectList(sql);
    return getOne(list);
  }

  /**
   * 查询返回一个结果，多个结果时抛出异常
   *
   * @param sql   执行的sql
   * @param value 参数
   * @return
   */
  public Map<String, Object> selectOne(String sql, Object value) {
    List<Map<String, Object>> list = selectList(sql, value);
    return getOne(list);
  }

  /**
   * 查询返回一个结果，多个结果时抛出异常
   *
   * @param sql        执行的sql
   * @param resultType 返回的结果类型
   * @param <T>        泛型类型
   * @return
   */
  public <T> T selectOne(String sql, Class<T> resultType) {
    List<T> list = selectList(sql, resultType);
    return getOne(list);
  }

  /**
   * 查询返回一个结果，多个结果时抛出异常
   *
   * @param sql        执行的sql
   * @param value      参数
   * @param resultType 返回的结果类型
   * @param <T>        泛型类型
   * @return
   */
  public <T> T selectOne(String sql, Object value, Class<T> resultType) {
    List<T> list = selectList(sql, value, resultType);
    return getOne(list);
  }

  /**
   * 查询返回List<Map<String, Object>>
   *
   * @param sql 执行的sql
   * @return
   */
  public List<Map<String, Object>> selectList(String sql) {
    String msId = msUtils.select(sql);
    return sqlSession.selectList(msId);
  }

  /**
   * 查询返回List<Map<String, Object>>
   *
   * @param sql   执行的sql
   * @param value 参数
   * @return
   */
  public List<Map<String, Object>> selectList(String sql, Object value) {
    Class<?> parameterType = value != null ? value.getClass() : null;
    String msId = msUtils.selectDynamic(sql, parameterType);
    return sqlSession.selectList(msId, value);
  }

  /**
   * 查询返回指定的结果类型
   *
   * @param sql        执行的sql
   * @param resultType 返回的结果类型
   * @param <T>        泛型类型
   * @return
   */
  public <T> List<T> selectList(String sql, Class<T> resultType) {
    String msId;
    if (resultType == null) {
      msId = msUtils.select(sql);
    } else {
      msId = msUtils.select(sql, resultType);
    }
    return sqlSession.selectList(msId);
  }

  /**
   * 查询返回指定的结果类型
   *
   * @param sql        执行的sql
   * @param value      参数
   * @param resultType 返回的结果类型
   * @param <T>        泛型类型
   * @return
   */
  public <T> List<T> selectList(String sql, Object value, Class<T> resultType) {
    String msId;
    Class<?> parameterType = value != null ? value.getClass() : null;
    if (resultType == null) {
      msId = msUtils.selectDynamic(sql, parameterType);
    } else {
      msId = msUtils.selectDynamic(sql, parameterType, resultType);
    }
    return sqlSession.selectList(msId, value);
  }

  /**
   * 插入数据
   *
   * @param sql 执行的sql
   * @return
   */
  public int insert(String sql) {
    String msId = msUtils.insert(sql);
    return sqlSession.insert(msId);
  }

  /**
   * 插入数据
   *
   * @param sql   执行的sql
   * @param value 参数
   * @return
   */
  public int insert(String sql, Object value) {
    Class<?> parameterType = value != null ? value.getClass() : null;
    String msId = msUtils.insertDynamic(sql, parameterType);
    return sqlSession.insert(msId, value);
  }

  /**
   * 更新数据
   *
   * @param sql 执行的sql
   * @return
   */
  public int update(String sql) {
    String msId = msUtils.update(sql);
    return sqlSession.update(msId);
  }

  /**
   * 更新数据
   *
   * @param sql   执行的sql
   * @param value 参数
   * @return
   */
  public int update(String sql, Object value) {
    Class<?> parameterType = value != null ? value.getClass() : null;
    String msId = msUtils.updateDynamic(sql, parameterType);
    return sqlSession.update(msId, value);
  }

  /**
   * 删除数据
   *
   * @param sql 执行的sql
   * @return
   */
  public int delete(String sql) {
    String msId = msUtils.delete(sql);
    return sqlSession.delete(msId);
  }

  /**
   * 删除数据
   *
   * @param sql   执行的sql
   * @param value 参数
   * @return
   */
  public int delete(String sql, Object value) {
    Class<?> parameterType = value != null ? value.getClass() : null;
    String msId = msUtils.deleteDynamic(sql, parameterType);
    return sqlSession.delete(msId, value);
  }


  private class MSUtils {
    private Configuration configuration;
    private LanguageDriver languageDriver;

    private MSUtils(Configuration configuration) {
      this.configuration = configuration;
      languageDriver = configuration.getDefaultScriptingLanuageInstance();
    }

    /**
     * 创建MSID
     *
     * @param sql 执行的sql
     * @param sql 执行的sqlCommandType
     * @return
     */
    private String newMsId(String sql, SqlCommandType sqlCommandType) {
      StringBuilder msIdBuilder = new StringBuilder(sqlCommandType.toString());
      msIdBuilder.append(".").append(sql.hashCode());
      return msIdBuilder.toString();
    }

    /**
     * 是否已经存在该ID
     *
     * @param msId
     * @return
     */
    private boolean hasMappedStatement(String msId) {
      return configuration.hasStatement(msId, false);
    }

    /**
     * 创建一个查询的MS
     *
     * @param msId
     * @param sqlSource  执行的sqlSource
     * @param resultType 返回的结果类型
     */
    private void newSelectMappedStatement(String msId, SqlSource sqlSource, final Class<?> resultType) {
      MappedStatement ms = new MappedStatement.Builder(configuration, msId, sqlSource, SqlCommandType.SELECT)
          .resultMaps(new ArrayList<ResultMap>() {
            {
              add(new ResultMap.Builder(configuration, "defaultResultMap", resultType, new ArrayList<ResultMapping>(0)).build());
            }
          })
          .build();
      //缓存
      configuration.addMappedStatement(ms);
    }

    /**
     * 创建一个简单的MS
     *
     * @param msId
     * @param sqlSource      执行的sqlSource
     * @param sqlCommandType 执行的sqlCommandType
     */
    private void newUpdateMappedStatement(String msId, SqlSource sqlSource, SqlCommandType sqlCommandType) {
      MappedStatement ms = new MappedStatement.Builder(configuration, msId, sqlSource, sqlCommandType)
          .resultMaps(new ArrayList<ResultMap>() {
            {
              add(new ResultMap.Builder(configuration, "defaultResultMap", int.class, new ArrayList<ResultMapping>(0)).build());
            }
          })
          .build();
      //缓存
      configuration.addMappedStatement(ms);
    }

    private String select(String sql) {
      String msId = newMsId(sql, SqlCommandType.SELECT);
      if (hasMappedStatement(msId)) {
        return msId;
      }
      StaticSqlSource sqlSource = new StaticSqlSource(configuration, sql);
      newSelectMappedStatement(msId, sqlSource, Map.class);
      return msId;
    }

    private String selectDynamic(String sql, Class<?> parameterType) {
      String msId = newMsId(sql + parameterType, SqlCommandType.SELECT);
      if (hasMappedStatement(msId)) {
        return msId;
      }
      SqlSource sqlSource = languageDriver.createSqlSource(configuration, sql, parameterType);
      newSelectMappedStatement(msId, sqlSource, Map.class);
      return msId;
    }

    private String select(String sql, Class<?> resultType) {
      String msId = newMsId(resultType + sql, SqlCommandType.SELECT);
      if (hasMappedStatement(msId)) {
        return msId;
      }
      StaticSqlSource sqlSource = new StaticSqlSource(configuration, sql);
      newSelectMappedStatement(msId, sqlSource, resultType);
      return msId;
    }

    private String selectDynamic(String sql, Class<?> parameterType, Class<?> resultType) {
      String msId = newMsId(resultType + sql + parameterType, SqlCommandType.SELECT);
      if (hasMappedStatement(msId)) {
        return msId;
      }
      SqlSource sqlSource = languageDriver.createSqlSource(configuration, sql, parameterType);
      newSelectMappedStatement(msId, sqlSource, resultType);
      return msId;
    }

    private String insert(String sql) {
      String msId = newMsId(sql, SqlCommandType.INSERT);
      if (hasMappedStatement(msId)) {
        return msId;
      }
      StaticSqlSource sqlSource = new StaticSqlSource(configuration, sql);
      newUpdateMappedStatement(msId, sqlSource, SqlCommandType.INSERT);
      return msId;
    }

    private String insertDynamic(String sql, Class<?> parameterType) {
      String msId = newMsId(sql + parameterType, SqlCommandType.INSERT);
      if (hasMappedStatement(msId)) {
        return msId;
      }
      SqlSource sqlSource = languageDriver.createSqlSource(configuration, sql, parameterType);
      newUpdateMappedStatement(msId, sqlSource, SqlCommandType.INSERT);
      return msId;
    }

    private String update(String sql) {
      String msId = newMsId(sql, SqlCommandType.UPDATE);
      if (hasMappedStatement(msId)) {
        return msId;
      }
      StaticSqlSource sqlSource = new StaticSqlSource(configuration, sql);
      newUpdateMappedStatement(msId, sqlSource, SqlCommandType.UPDATE);
      return msId;
    }

    private String updateDynamic(String sql, Class<?> parameterType) {
      String msId = newMsId(sql + parameterType, SqlCommandType.UPDATE);
      if (hasMappedStatement(msId)) {
        return msId;
      }
      SqlSource sqlSource = languageDriver.createSqlSource(configuration, sql, parameterType);
      newUpdateMappedStatement(msId, sqlSource, SqlCommandType.UPDATE);
      return msId;
    }

    private String delete(String sql) {
      String msId = newMsId(sql, SqlCommandType.DELETE);
      if (hasMappedStatement(msId)) {
        return msId;
      }
      StaticSqlSource sqlSource = new StaticSqlSource(configuration, sql);
      newUpdateMappedStatement(msId, sqlSource, SqlCommandType.DELETE);
      return msId;
    }

    private String deleteDynamic(String sql, Class<?> parameterType) {
      String msId = newMsId(sql + parameterType, SqlCommandType.DELETE);
      if (hasMappedStatement(msId)) {
        return msId;
      }
      SqlSource sqlSource = languageDriver.createSqlSource(configuration, sql, parameterType);
      newUpdateMappedStatement(msId, sqlSource, SqlCommandType.DELETE);
      return msId;
    }
  }
}